// from SDL/src/atomic/SDL_atomic.c
#include <stdbool.h>

typedef struct {
    int value;
} Atomic_t;

bool atomic_CAS(Atomic_t * a, int oldval, int newval)
{
    return (bool) __sync_bool_compare_and_swap(&a->value, oldval, newval);
}

/* 以下是利用CAS实现的三个原子操作: add, get, set */

int atomic_add(Atomic_t * a, int v)
{
#if defined(HAVE_GCC_ATOMICS)
    // fetch and add 直接符合原子add的语义
    return __sync_fetch_and_add(&a->value, v);
#else
    int value;
    do {
        value = a->value;
    } while (!atomic_CAS(a, value, (value + v)));
    return value;
#endif
}

int atomic_get(Atomic_t * a)
{
    int value;
    do {
        value = a->value;
    } while (!atomic_CAS(a, value, value));
    return value;
}

int atomic_set(Atomic_t * a, int v)
{
#if defined(HAVE_GCC_ATOMICS)
    // test and set 直接符合原子set的语义
    return __sync_lock_test_and_set(&a->value, v);
#else
    int value;
    do {
        value = a->value;
    } while (!atomic_CAS(a, value, v));
    return value;
#endif
}

#ifndef atomic_incref
#define atomic_incref(a)    atomic_add(a, 1)
#endif

#ifndef atomic_decref
#define atomic_decref(a)    (atomic_add(a, -1) == 1)
#endif

/* 利用CAS实现自旋锁, 和sdl中实现不一样 */

typedef Atomic_t SpinLock;

bool Atomic_trylock(SpinLock * lock)
{
#if defined(HAVE_GCC_ATOMICS)
    return (__sync_lock_test_and_set(&lock->value, 1) == 0);
#else
    return atomic_CAS(lock, 0, 1);
#endif
}

void atomic_lock(SpinLock * lock)
{
    // 注: 如果自旋锁一直没有解锁,则将一直消耗cpu资源,进入死循环
    while(!atomic_CAS(lock, 0, 1)) {};
}

void atomic_unlock(SpinLock * lock)
{
#if defined(HAVE_GCC_ATOMICS)
    __sync_lock_release(&lock->value);
#else
    lock->value = 0;
#endif
}

/* 互斥锁(mutex)
 * 与CAS没直接关系, 这里因为是锁相关的内容所以放到一块, 这里直接用pthread库实现互斥锁
 * 在实现互斥锁之前, 让我们先实现(封装)一堆内存管理的函数, 用到了atomic_add
 */

#include <stdlib.h>

typedef void * (* malloc_func)(size_t size);
typedef void * (* calloc_func)(size_t nmemb, size_t size);
typedef void * (* realloc_func)(void *mem, size_t size);
typedef void   (* free_func)(void *mem);

static struct {
    malloc_func malloc_fn;
    calloc_func calloc_fn;
    realloc_func realloc_fn;
    free_func free_fn;
    Atomic_t num_allocations; /* 用于统计所有分配的内存数量 */
} s_mem = {
    malloc, calloc, realloc, free, {0}
};

void * mem_malloc(size_t size)
{
    void * mem;

    if (!size) {
        size = 1;
    }
    mem = s_mem.malloc_fn(size);
    if (mem) {
        atomic_incref(&s_mem.num_allocations);
    }
    return mem;
}

void * mem_calloc(size_t nmemb, size_t size)
{
    void * mem;

    // when nmemb == 0 || size == 0
    if (!nmemb || !size) {
        nmemb = 1;
        size = 1;
    }
    mem = s_mem.calloc_fn(nmemb, size);
    if (mem) {
        atomic_incref(&s_mem.num_allocations);
    }
    return mem;
}

void * mem_realloc(void * ptr, size_t size)
{
    void * mem;

    // when ptr is NULL and size == 0
    if (!ptr && !size) {
        size = 1;
    }
    mem = s_mem.realloc_fn(ptr, size);
    if (mem) {
        atomic_incref(&s_mem.num_allocations);
    }
    return mem;
}

void * mem_free(void * ptr)
{
    if (!ptr) {
        return NULL;
    }
    s_mem.free_fn(ptr);
    atomic_decref(&s_mem.num_allocations);
    return NULL;
}

/* 下面开始实现互斥锁相关的函数, 利用了pthread */

#include <stdio.h>
#include <pthread.h>

typedef struct {
    pthread_mutex_t id;
} Mutex;

Mutex * mutex_create(void)
{
    Mutex * mutex;
    pthread_mutexattr_t attr;

    // allocate the structure
    mutex = (Mutex *) mem_calloc(1, sizeof(*mutex));
    if (mutex) {
        pthread_mutexattr_init(&attr);
        if (pthread_mutex_init(&mutex->id, &attr) != 0) {
            printf("pthread_mutex_init() failed\n");
            mem_free(mutex);
            mutex = NULL;
        }
    } else {
        printf("failed to allocate memory\n");
        return NULL;
    }

    return mutex;
}

void mutex_destroy(Mutex * mutex)
{
    if (mutex) {
        pthread_mutex_destroy(&mutex->id);
        mem_free(mutex);
    }
}

int mutex_lock(Mutex * mutex)
{
    if (mutex == NULL) {
        printf("passed a NULL mutex\n");
        return -1;
    }
    if (pthread_mutex_lock(&mutex->id) != 0) {
        printf("pthread_mutex_lock() failed\n");
        return -2;
    }

    return 0;
}

#define EBUSY       16  /* Device or resource busy */
#define MUTEX_TIMEDOUT 1

int mutex_trylock(Mutex * mutex)
{
    int result;

    if (mutex == NULL) {
        printf("passed a NULL mutex\n");
        return -1;
    }

    result = pthread_mutex_trylock(&mutex->id);
    if (result != 0) {
        if (result == EBUSY) {
            return MUTEX_TIMEDOUT;
        } else {
            printf("pthread_mutex_trylock() failed\n");
            return -3;
        }
    }
    return 0;
}

int mutex_unlock(Mutex * mutex)
{
    if (mutex == NULL) {
        printf("passed a NULL mutex\n");
        return -1;
    }

    if (pthread_mutex_unlock(&mutex->id) != 0) {
        printf("pthread_mutex_unlock() failed\n");
        return -4;
    }
    return 0;
}
